#!/usr/bin/env python3
"""
动态关键词提取和存储工具

智能的关键词提取工具，能够从编程题目描述中自动提取关键词，
并存储到JSON规则文件中，用于辅助规则引擎的问题类型识别。

主要功能：
1. 多语言关键词提取：支持中文、英文关键词提取
2. 专业术语识别：数学符号、编程术语、上下文关键词
3. 智能权重计算：根据关键词类型和频率计算权重
4. 模式名称建议：基于内容自动建议问题模式名称
5. 批量处理：支持多个文本的批量关键词提取
6. 规则管理：自动更新JSON规则文件和权重文件

技术特点：
- 使用jieba进行中文分词和词性标注
- 支持多种关键词类型和权重策略
- 智能停用词过滤
- 频率统计和权重调整


依赖: jieba, json, re, collections
"""

import re
import json
import os
from typing import Dict, List, Any, Set, Tuple
from collections import Counter
import jieba
import jieba.posseg as pseg

class DynamicKeywordExtractor:
    """
    动态关键词提取器
    
    项目的辅助工具，主要用于帮助开发者从编程题目中提取关键词，
    然后手动添加到JSON规则文件中，以扩展规则库的覆盖范围。
    
    核心功能：
    1. 多维度关键词提取：中文、英文、数学、编程、上下文
    2. 智能权重计算：基于关键词类型、长度、位置、频率
    3. 模式名称建议：根据内容自动推荐问题类型
    4. 批量处理：支持多个文本的批量提取
    5. 规则管理：自动更新JSON文件和权重文件
    
    使用场景：
    - 开发者需要为新的问题类型添加关键词
    - 批量处理大量题目描述
    - 分析题目中的关键词分布
    - 扩展规则库的覆盖范围
    
    注意：这是一个辅助工具，提取的关键词需要人工审核后再添加到规则库中。
    """
    
    def __init__(self, rules_dir: str = "rules"):
        """
        初始化动态关键词提取器
        
        参数:
            rules_dir (str): 规则文件目录路径，默认为"rules"
            
        属性:
            rules_dir (str): 规则文件目录
            extracted_keywords (Dict): 存储提取的关键词结果
            keyword_frequency (Counter): 关键词频率统计
            context_keywords (Dict): 上下文关键词
            existing_rules (Dict): 现有规则数据
        """
        self.rules_dir = rules_dir
        self.extracted_keywords = {}      # 存储提取的关键词结果
        self.keyword_frequency = Counter() # 关键词频率统计
        self.context_keywords = {}        # 上下文关键词
        
        # 加载现有规则 - 用于避免重复提取和权重计算
        self._load_existing_rules()
    
    def _load_existing_rules(self):
        """
        加载现有规则文件
        
        从rules目录加载keywords.json文件，用于：
        1. 避免重复提取已存在的关键词
        2. 了解现有规则库的结构
        3. 为权重计算提供参考
        
        如果文件不存在或加载失败，会创建空的规则结构。
        """
        try:
            keywords_file = os.path.join(self.rules_dir, "keywords.json")
            if os.path.exists(keywords_file):
                with open(keywords_file, 'r', encoding='utf-8') as f:
                    self.existing_rules = json.load(f)
                print(f"已加载现有规则文件: {len(self.existing_rules.get('patterns', {}))} 个模式")
            else:
                self.existing_rules = {"patterns": {}}
                print("规则文件不存在，将创建新的规则结构")
        except Exception as e:
            print(f"加载现有规则时出错: {e}")
            self.existing_rules = {"patterns": {}}
    
    def extract_keywords_from_text(self, text: str, pattern_name: str = None) -> Dict[str, Any]:
        """
        从文本中提取关键词
        
        这是提取器的核心方法，通过多个维度从文本中提取关键词：
        1. 中文关键词：使用jieba分词和词性标注
        2. 英文关键词：正则表达式提取英文单词
        3. 数学关键词：数学符号和术语
        4. 编程关键词：编程术语和概念
        5. 上下文关键词：操作动词和结果描述词
        
        参数:
            text (str): 要分析的文本内容
            pattern_name (str, optional): 模式名称，用于存储提取结果
            
        返回:
            Dict[str, int]: 关键词及其权重的字典
                - key: 关键词
                - value: 权重值（1-15之间）
        """
        print(f"从文本中提取关键词: {text}")
        
        # 1. 中文分词和词性标注 - 使用jieba进行中文处理
        chinese_keywords = self._extract_chinese_keywords(text)
        
        # 2. 英文关键词提取 - 正则表达式提取英文单词
        english_keywords = self._extract_english_keywords(text)
        
        # 3. 数学符号和操作符 - 识别数学相关关键词
        math_keywords = self._extract_math_keywords(text)
        
        # 4. 编程术语 - 识别编程相关关键词
        programming_keywords = self._extract_programming_keywords(text)
        
        # 5. 上下文关键词 - 识别操作和结果描述词
        context_keywords = self._extract_context_keywords(text)
        
        # 合并所有关键词 - 按类别组织
        all_keywords = {
            "chinese": chinese_keywords,
            "english": english_keywords,
            "math": math_keywords,
            "programming": programming_keywords,
            "context": context_keywords
        }
        
        # 计算关键词权重 - 基于类型、长度、位置、频率等因素
        weighted_keywords = self._calculate_keyword_weights(all_keywords, text)
        
        # 存储提取结果 - 如果提供了模式名称
        if pattern_name:
            self.extracted_keywords[pattern_name] = {
                "text": text,
                "keywords": weighted_keywords,
                "timestamp": self._get_timestamp()
            }
        
        return weighted_keywords
    
    def _extract_chinese_keywords(self, text: str) -> List[str]:
        """
        提取中文关键词
        
        使用jieba分词和词性标注技术提取中文关键词：
        1. 分词：将文本切分成词语
        2. 词性标注：识别词语的词性（名词、动词、形容词等）
        3. 过滤：去除停用词和短词
        4. 去重：返回唯一的关键词列表
        
        参数:
            text (str): 要分析的中文文本
            
        返回:
            List[str]: 提取的中文关键词列表
        """
        keywords = []
        
        # 使用jieba分词 - 将文本切分成词语
        words = jieba.lcut(text)
        
        # 过滤停用词和短词 - 提高关键词质量
        stop_words = {
            "的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", 
            "一", "一个", "上", "也", "很", "到", "说", "要", "去", "你", 
            "会", "着", "没有", "看", "好", "自己", "这"
        }
        
        # 过滤长度小于2的词和停用词
        for word in words:
            if len(word) >= 2 and word not in stop_words:
                keywords.append(word)
        
        # 使用词性标注提取名词、动词、形容词 - 更精确的关键词提取
        pos_words = pseg.lcut(text)
        for word, flag in pos_words:
            # 只保留名词(n)、动词(v)、形容词(a)且长度>=2的词
            if flag.startswith(('n', 'v', 'a')) and len(word) >= 2 and word not in stop_words:
                keywords.append(word)
        
        return list(set(keywords))  # 去重并返回
    
    def _extract_english_keywords(self, text: str) -> List[str]:
        """
        提取英文关键词
        
        使用正则表达式提取文本中的英文单词，并过滤常见的英文停用词。
        这对于处理中英文混合的编程题目特别有用。
        
        参数:
            text (str): 要分析的文本内容
            
        返回:
            List[str]: 提取的英文关键词列表（小写）
        """
        keywords = []
        
        # 提取英文单词 - 使用正则表达式匹配连续的英文字母
        english_pattern = r'\b[a-zA-Z]+\b'
        english_words = re.findall(english_pattern, text)
        
        # 过滤常见英文停用词 - 提高关键词质量
        english_stop_words = {
            "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", 
            "for", "of", "with", "by", "is", "are", "was", "were", "be", 
            "been", "have", "has", "had", "do", "does", "did", "will", 
            "would", "could", "should"
        }
        
        # 过滤停用词和短词，转换为小写
        for word in english_words:
            if word.lower() not in english_stop_words and len(word) >= 2:
                keywords.append(word.lower())
        
        return list(set(keywords))  # 去重并返回
    
    def _extract_math_keywords(self, text: str) -> List[str]:
        """
        提取数学关键词
        
        识别文本中的数学符号和术语，这对于数学相关的编程题目非常重要。
        包括：
        1. 数学符号：+、-、*、/、=、!、^、%等
        2. 中文数学术语：加法、减法、阶乘、质数等
        3. 英文数学术语：addition、subtraction、factorial等
        
        参数:
            text (str): 要分析的文本内容
            
        返回:
            List[str]: 提取的数学关键词列表
        """
        keywords = []
        
        # 数学符号 - 识别基本的数学运算符
        math_symbols = ["+", "-", "*", "/", "=", "!", "^", "%", "(", ")", "[", "]", "{", "}"]
        for symbol in math_symbols:
            if symbol in text:
                keywords.append(symbol)
        
        # 中文数学术语 - 常见的数学概念和操作
        math_terms = [
            "加法", "减法", "乘法", "除法", "阶乘", "幂", "次方", "平方", "立方", 
            "根号", "分数", "小数", "整数", "自然数", "质数", "素数", 
            "最大公约数", "最小公倍数"
        ]
        for term in math_terms:
            if term in text:
                keywords.append(term)
        
        # 英文数学术语 - 对应的英文数学概念
        english_math_terms = [
            "addition", "subtraction", "multiplication", "division", "factorial", 
            "power", "square", "cube", "root", "fraction", "decimal", "integer", 
            "natural", "prime", "gcd", "lcm"
        ]
        for term in english_math_terms:
            if term.lower() in text.lower():
                keywords.append(term.lower())
        
        return list(set(keywords))  # 去重并返回
    
    def _extract_programming_keywords(self, text: str) -> List[str]:
        """
        提取编程关键词
        
        识别文本中的编程术语和概念，这对于算法和数据结构相关的题目很重要。
        包括：
        1. 中文编程术语：数组、列表、字符串、算法等
        2. 英文编程术语：array、list、string、algorithm等
        
        参数:
            text (str): 要分析的文本内容
            
        返回:
            List[str]: 提取的编程关键词列表
        """
        keywords = []
        
        # 中文编程术语 - 常见的编程概念和数据结构
        programming_terms = [
            "数组", "列表", "字符串", "字符", "变量", "函数", "方法", "算法", 
            "排序", "查找", "遍历", "循环", "条件", "判断", "递归", "迭代"
        ]
        for term in programming_terms:
            if term in text:
                keywords.append(term)
        
        # 英文编程术语 - 对应的英文编程概念
        english_programming_terms = [
            "array", "list", "string", "char", "variable", "function", "method", 
            "algorithm", "sort", "search", "loop", "condition", "recursion", "iteration"
        ]
        for term in english_programming_terms:
            if term.lower() in text.lower():
                keywords.append(term.lower())
        
        return list(set(keywords))  # 去重并返回
    
    def _extract_context_keywords(self, text: str) -> List[str]:
        """
        提取上下文关键词
        
        识别文本中的操作动词和结果描述词，这些关键词有助于理解题目的意图。
        包括：
        1. 操作动词：计算、求、找、统计、分析等
        2. 结果描述词：结果、答案、值、和、差、积等
        
        参数:
            text (str): 要分析的文本内容
            
        返回:
            List[str]: 提取的上下文关键词列表
        """
        keywords = []
        
        # 操作动词 - 描述题目要求的动作
        action_verbs = [
            "计算", "求", "找", "统计", "分析", "处理", "生成", "创建", 
            "输出", "输入", "显示", "打印"
        ]
        for verb in action_verbs:
            if verb in text:
                keywords.append(verb)
        
        # 结果描述词 - 描述期望的输出结果
        result_words = [
            "结果", "答案", "值", "和", "差", "积", "商", "最大值", "最小值", 
            "平均值", "总数", "个数"
        ]
        for word in result_words:
            if word in text:
                keywords.append(word)
        
        return list(set(keywords))  # 去重并返回
    
    def _calculate_keyword_weights(self, all_keywords: Dict[str, List[str]], text: str) -> Dict[str, int]:
        """
        计算关键词权重
        
        根据多个因素计算关键词的权重值，用于在规则引擎中确定关键词的重要性：
        1. 基础权重：根据关键词类型设定（数学>编程>中文>英文>上下文）
        2. 长度权重：较长的关键词通常更重要
        3. 位置权重：出现在文本开头或结尾的关键词更重要
        4. 频率权重：出现频率高的关键词权重更高
        
        参数:
            all_keywords (Dict[str, List[str]]): 按类别分组的关键词
            text (str): 原始文本内容
            
        返回:
            Dict[str, int]: 关键词及其权重的字典
        """
        weighted_keywords = {}
        
        # 权重规则 - 不同类型关键词的基础权重
        weight_rules = {
            "math": 10,        # 数学关键词权重最高 - 对问题类型识别最重要
            "programming": 8,  # 编程关键词权重较高 - 算法和数据结构相关
            "chinese": 6,      # 中文关键词权重中等 - 语义理解
            "english": 5,      # 英文关键词权重中等 - 国际化支持
            "context": 4       # 上下文关键词权重较低 - 辅助信息
        }
        
        # 为每个类别的关键词计算权重
        for category, keywords in all_keywords.items():
            base_weight = weight_rules.get(category, 5)
            
            for keyword in keywords:
                # 基础权重
                weight = base_weight
                
                # 根据关键词长度调整权重 - 长词通常更具体
                if len(keyword) >= 3:
                    weight += 1
                
                # 根据在文本中的位置调整权重 - 开头和结尾的词更重要
                if text.startswith(keyword) or text.endswith(keyword):
                    weight += 1
                
                # 根据出现频率调整权重 - 频繁出现的词更重要
                if keyword in self.keyword_frequency:
                    self.keyword_frequency[keyword] += 1
                    if self.keyword_frequency[keyword] > 5:
                        weight += 1
                else:
                    self.keyword_frequency[keyword] = 1
                
                weighted_keywords[keyword] = weight
        
        return weighted_keywords
    
    def _get_timestamp(self) -> str:
        """
        获取当前时间戳
        
        用于记录关键词提取的时间，便于追踪和管理提取历史。
        
        返回:
            str: 格式化的时间戳字符串 (YYYY-MM-DD HH:MM:SS)
        """
        from datetime import datetime
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    def suggest_pattern_name(self, text: str) -> str:
        """
        根据文本内容建议模式名称
        
        基于文本中的关键词自动建议问题模式名称，帮助开发者快速分类问题。
        通过匹配预定义的关键词模式来确定最合适的问题类型。
        
        参数:
            text (str): 要分析的文本内容
            
        返回:
            str: 建议的模式名称，如果无法匹配则返回"unknown"
        """
        text_lower = text.lower()
        
        # 基于关键词建议模式名称 - 预定义的问题类型和对应关键词
        suggestions = {
            "addition": ["两个数", "相加", "和", "addition", "加法"],
            "subtraction": ["两个数", "相减", "差", "subtraction", "减法"],
            "multiplication": ["两个数", "相乘", "积", "multiplication", "乘法"],
            "division": ["两个数", "相除", "商", "division", "除法"],
            "factorial": ["阶乘", "factorial", "!", "n!"],
            "string_char_count": ["统计", "字母", "字符", "出现次数"],
            "array_max": ["最大值", "最大", "max", "maximum"],
            "array_min": ["最小值", "最小", "min", "minimum"],
            "sort": ["排序", "升序", "降序", "sort"],
            "reverse": ["反转", "逆序", "reverse", "倒序"],
            "palindrome": ["回文", "palindrome", "回文字符串"]
        }
        
        best_match = "unknown"
        best_score = 0
        
        # 计算每个模式名称的匹配分数
        for pattern_name, keywords in suggestions.items():
            score = 0
            for keyword in keywords:
                if keyword.lower() in text_lower:
                    score += 1
            
            # 选择得分最高的模式
            if score > best_score:
                best_score = score
                best_match = pattern_name
        
        return best_match
    
    def save_extracted_keywords(self, pattern_name: str = None):
        """
        保存提取的关键词到JSON文件
        
        将提取的关键词保存到keywords.json和weights.json文件中，用于扩展规则库。
        这是提取器的核心功能之一，但需要人工审核后再使用。
        
        注意：此方法会自动修改规则文件，建议在人工审核后使用。
        
        参数:
            pattern_name (str, optional): 要保存的模式名称
            
        返回:
            bool: 保存是否成功
        """
        try:
            # 更新现有规则
            if pattern_name and pattern_name in self.extracted_keywords:
                extracted_data = self.extracted_keywords[pattern_name]
                keywords = extracted_data["keywords"]
                
                # 更新keywords.json - 添加新的模式或更新现有模式
                if pattern_name not in self.existing_rules["patterns"]:
                    self.existing_rules["patterns"][pattern_name] = {
                        "keywords": [],
                        "description": f"从文本 '{extracted_data['text']}' 自动提取",
                        "category": "auto_extracted",
                        "priority": 5
                    }
                
                # 添加新关键词 - 避免重复添加
                for keyword, weight in keywords.items():
                    if keyword not in self.existing_rules["patterns"][pattern_name]["keywords"]:
                        self.existing_rules["patterns"][pattern_name]["keywords"].append(keyword)
                
                # 保存到文件
                keywords_file = os.path.join(self.rules_dir, "keywords.json")
                with open(keywords_file, 'w', encoding='utf-8') as f:
                    json.dump(self.existing_rules, f, ensure_ascii=False, indent=4)
                
                print(f"已保存提取的关键词到模式 '{pattern_name}'")
                
                # 更新权重文件 - 同步更新权重信息
                self._update_weights_file(keywords)
                
                return True
            else:
                print("没有找到要保存的关键词")
                return False
                
        except Exception as e:
            print(f"保存关键词时出错: {e}")
            return False
    
    def _update_weights_file(self, keywords: Dict[str, int]):
        """
        更新权重文件
        
        将关键词权重信息保存到weights.json文件中，与keywords.json文件保持同步。
        权重信息用于规则引擎中的关键词匹配和评分。
        
        参数:
            keywords (Dict[str, int]): 关键词及其权重的字典
        """
        try:
            weights_file = os.path.join(self.rules_dir, "weights.json")
            
            # 加载现有权重文件或创建新的
            if os.path.exists(weights_file):
                with open(weights_file, 'r', encoding='utf-8') as f:
                    weights_data = json.load(f)
            else:
                weights_data = {"weights": {}}
            
            # 更新权重 - 添加或更新关键词权重
            for keyword, weight in keywords.items():
                weights_data["weights"][keyword] = weight
            
            # 保存权重文件
            with open(weights_file, 'w', encoding='utf-8') as f:
                json.dump(weights_data, f, ensure_ascii=False, indent=4)
            
            print(f"已更新权重文件")
            
        except Exception as e:
            print(f"更新权重文件时出错: {e}")
    
    def batch_extract_from_texts(self, texts: List[str]) -> Dict[str, Any]:
        """
        批量从多个文本中提取关键词
        
        这是提取器的高级功能，可以一次性处理多个文本，自动建议模式名称，
        并提取关键词。适用于批量处理大量题目描述的场景。
        
        参数:
            texts (List[str]): 要分析的文本列表
            
        返回:
            Dict[str, Any]: 批量提取结果，包含每个文本的：
                - text: 原始文本
                - suggested_pattern: 建议的模式名称
                - keywords: 提取的关键词及其权重
        """
        results = {}
        
        for i, text in enumerate(texts):
            print(f"\n处理文本 {i+1}: {text}")
            
            # 建议模式名称 - 自动推荐问题类型
            suggested_pattern = self.suggest_pattern_name(text)
            print(f"建议的模式名称: {suggested_pattern}")
            
            # 提取关键词 - 使用建议的模式名称
            keywords = self.extract_keywords_from_text(text, f"{suggested_pattern}_{i+1}")
            
            # 存储结果
            results[f"{suggested_pattern}_{i+1}"] = {
                "text": text,
                "suggested_pattern": suggested_pattern,
                "keywords": keywords
            }
        
        return results
    
    def get_extraction_statistics(self) -> Dict[str, Any]:
        """
        获取提取统计信息
        
        提供提取器的使用统计信息，包括：
        1. 总提取次数
        2. 总关键词数量
        3. 最频繁的关键词
        4. 提取历史记录
        
        返回:
            Dict[str, Any]: 统计信息字典
                - total_extractions: 总提取次数
                - total_keywords: 总关键词数量
                - most_frequent_keywords: 最频繁的关键词列表
                - extraction_history: 提取历史记录
        """
        stats = {
            "total_extractions": len(self.extracted_keywords),
            "total_keywords": len(self.keyword_frequency),
            "most_frequent_keywords": self.keyword_frequency.most_common(10),
            "extraction_history": []
        }
        
        # 构建提取历史记录
        for pattern_name, data in self.extracted_keywords.items():
            stats["extraction_history"].append({
                "pattern": pattern_name,
                "text": data["text"],
                "timestamp": data["timestamp"],
                "keyword_count": len(data["keywords"])
            })
        
        return stats

def demo_keyword_extractor():
    """
    演示关键词提取器的完整功能
    
    这个函数展示了动态关键词提取器的所有核心功能：
    1. 批量文本处理：一次性处理多个题目描述
    2. 多维度关键词提取：中文、英文、数学、编程、上下文
    3. 智能模式名称建议：自动推荐问题类型
    4. 权重计算：基于多种因素计算关键词权重
    5. 规则保存：将提取结果保存到JSON文件
    6. 统计信息：展示提取器的使用统计
    
    测试用例涵盖了常见的编程问题类型，验证提取功能的准确性。
    """
    print("=" * 80)
    print("动态关键词提取器演示")
    print("=" * 80)
    
    # 创建提取器实例
    extractor = DynamicKeywordExtractor()
    
    # 测试文本 - 涵盖不同类型的编程问题
    test_texts = [
        "计算两个数的加法",                    # 数学运算
        "求阶乘的值",                        # 数学函数
        "统计字符串中字母出现次数",            # 字符串处理
        "找到数组中的最大值",                # 数组操作
        "判断一个数是否为质数",              # 数学判断
        "计算斐波那契数列的第n项"            # 递归算法
    ]
    
    # 批量提取关键词 - 展示批量处理能力
    results = extractor.batch_extract_from_texts(test_texts)
    
    # 显示提取结果 - 展示多维度关键词提取
    print("\n" + "=" * 50)
    print("提取结果")
    print("=" * 50)
    
    for pattern_name, result in results.items():
        print(f"\n模式: {pattern_name}")
        print(f"文本: {result['text']}")
        print(f"建议模式: {result['suggested_pattern']}")
        print("关键词:")
        for keyword, weight in result['keywords'].items():
            print(f"  {keyword}: {weight}")
    
    # 保存提取的关键词 - 展示规则管理功能
    print("\n" + "=" * 50)
    print("保存提取的关键词")
    print("=" * 50)
    
    for pattern_name in results.keys():
        extractor.save_extracted_keywords(pattern_name)
    
    # 显示统计信息 - 展示使用统计
    print("\n" + "=" * 50)
    print("统计信息")
    print("=" * 50)
    
    stats = extractor.get_extraction_statistics()
    print(f"总提取次数: {stats['total_extractions']}")
    print(f"总关键词数: {stats['total_keywords']}")
    print("最频繁的关键词:")
    for keyword, count in stats['most_frequent_keywords']:
        print(f"  {keyword}: {count}")

if __name__ == "__main__":
    """
    主程序入口
    
    当直接运行此文件时，会执行关键词提取器的完整演示。
    演示内容包括：
    1. 批量文本处理
    2. 多维度关键词提取
    3. 智能模式名称建议
    4. 权重计算和规则保存
    5. 统计信息展示
    
    这个演示帮助用户了解如何使用提取器来扩展规则库。
    """
    demo_keyword_extractor()
